#include "gdt.h"
#include <stddef.h>
#include <stdint.h>

// https://wiki.osdev.org/GDT

// GDT entry structure
// addr_hi : flags2 : size_hi : flags1 : addr_mid : addr_lo : size_lo

// addr_hi : starting address high : 8-bits

// flags2 : flags : 8-bits
// 		0: accessed bit (set by CPU)
//      1: writable for DS; readable for CS
//      2: direction / conforming:
// 			direction for DS:
//				0 -> segment growns down, 1 -> segment grows up
//      	conforming for CS: 1 -> code in segment can be executed 
//                                  from equal or lower privilege level
//                             0 -> only executed from the ring set in the privilege bits
//      3: executable bit : 1 -> CS, 0 -> DS
//      4: always 1
//      5-6: privilege bits (contains ring level)
//      7: present bit : 1 -> valid

// size_hi : length of this segment : 16-bits
// flags1: flags : 4-bits
// 		0: 0
// 		1: 0
//		2: selector size : 0 -> 16-bit, 1 -> 32-bit
//		3: granularity : 0 -> 1b, 1 -> 4KiB

uint64_t gdt[3];
struct {
	uint16_t size;
	uint32_t offset;
} __attribute__((packed)) gdt_basep = {
	sizeof(gdt) - 1,
	(uint32_t)&gdt,
};

extern void flush_gdt(void);

void set_gdt_entry(size_t i, uint32_t offset, uint32_t size,
		   uint8_t access_flags, uint8_t size_flags) {
	gdt[i] = size & 0xffff;
	gdt[i] |= (offset & 0xffff) << 16;
	gdt[i] |= (uint64_t)((offset >> 16) & 0xff) << 32;
	gdt[i] |= (uint64_t)((size >> 16) & 0b1111) << 48;

	gdt[i] |= (uint64_t)access_flags << 40;
	gdt[i] |= (uint64_t)size_flags << 52;
}

void install_gdt(void) {
	set_gdt_entry(0, 0, 0, 0, 0);

	// documentation of 0b10011010(flags2) and 0b1100(flags1) is at the top of this file
	set_gdt_entry(1, 0, 0xffffffff, 0b10011010, 0b1100);

	// documentation of 0b10010010(flags2) and 0b1100(flags1) is at the top of this file
	set_gdt_entry(2, 0, 0xffffffff, 0b10010010, 0b1100);

	// @todo: CS & DS descriptors for user mode
	flush_gdt();
}
